package com.example.lbs.service;

import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.GeoOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.domain.geo.GeoReference;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class ShopGeoService {
    private final StringRedisTemplate redis;

    public ShopGeoService(StringRedisTemplate redis) {
        this.redis = redis;
    }

    private String geoKey(String brand) {
        return brand != null && !brand.isBlank() ? "geo:shops:" + brand.toLowerCase() : "geo:shops:all";
    }

    public void addShopGeo(Long shopId, double lng, double lat, String brand) {
        String key = geoKey(brand);
        redis.opsForGeo().add(key, new Point(lng, lat), shopId.toString());
        // 也加入全量集合，便于统一查询
        if (brand != null && !brand.isBlank()) {
            redis.opsForGeo().add(geoKey(null), new Point(lng, lat), shopId.toString());
        }
    }

    public void removeShopGeo(Long shopId, String brand) {
        String key = geoKey(brand);
        redis.opsForZSet().remove(key, shopId.toString());
        redis.opsForZSet().remove(geoKey(null), shopId.toString());
    }

    public List<NearbyShop> searchNearby(double lng, double lat, double radiusMeters, int limit, String brand) {
        // 参数校验
        if (lng < -180 || lng > 180 || lat < -90 || lat > 90) {
            throw new IllegalArgumentException("Invalid longitude or latitude values.");
        }
        if (radiusMeters <= 0 || limit <= 0) {
            throw new IllegalArgumentException("Radius and limit must be positive.");
        }
        if (brand == null || brand.trim().isEmpty()) {
            throw new IllegalArgumentException("Brand cannot be null or empty.");
        }

        String key = geoKey(brand);
        GeoOperations<String, String> ops = redis.opsForGeo();
        double radiusKm = radiusMeters / 1000.0;
        RedisGeoCommands.GeoSearchCommandArgs args = RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs()
                .includeDistance().includeCoordinates().sortAscending().limit(limit);
        GeoResults<RedisGeoCommands.GeoLocation<String>> results = ops.search(
                key,
                GeoReference.fromCoordinate(lng, lat),
                new Distance(radiusKm, Metrics.KILOMETERS),
                args
        );
        if (results == null || results.getContent() == null) return List.of();
        return results.getContent().stream().map(r -> {
            NearbyShop s = new NearbyShop();
            s.setShopId(Long.valueOf(r.getContent().getName()));
            s.setDistanceMeters(r.getDistance() != null ? r.getDistance().getValue() : null);
            s.setLng(r.getContent().getPoint().getX());
            s.setLat(r.getContent().getPoint().getY());
            return s;
        }).toList();
    }

    public static class NearbyShop {
        private Long shopId;
        private Double distanceMeters;
        private Double lng;
        private Double lat;

        public Long getShopId() { return shopId; }
        public void setShopId(Long shopId) { this.shopId = shopId; }
        public Double getDistanceMeters() { return distanceMeters; }
        public void setDistanceMeters(Double distanceMeters) { this.distanceMeters = distanceMeters; }
        public Double getLng() { return lng; }
        public void setLng(Double lng) { this.lng = lng; }
        public Double getLat() { return lat; }
        public void setLat(Double lat) { this.lat = lat; }
    }
}